package ace.module.account.core.impl.service.authentication.impl;

import ace.cmp.core.exception.BusinessException;
import ace.cmp.core.model.R;
import ace.cmp.i18n.api.MessageSourceAssertService;
import ace.cmp.json.api.JsonService;
import ace.module.account.api.model.auth.dto.AuthenticationResult;
import ace.module.account.api.model.auth.input.AuthenticationInputMap;
import ace.module.account.api.model.auth.input.AuthenticationParams;
import ace.module.account.core.impl.dao.entity.IamApplication;
import ace.module.account.core.impl.manager.IamApplicationManager;
import ace.module.account.core.impl.service.authentication.AuthenticationService;
import ace.module.account.core.impl.service.authentication.handler.AuthenticationHandler;
import ace.module.account.core.impl.service.authentication.handler.AuthenticationHandlerManager;
import ace.module.account.core.impl.service.authentication.handler.model.input.AuthenticationHandlerContextInput;
import ace.module.account.core.impl.service.authentication.model.bo.AuthenticationContextBo;
import ace.module.security.api.constant.SecurityConstant;
import jakarta.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.springframework.stereotype.Component;

/**
 * @author caspar
 * @date 2023/3/16 9:27
 */
@RequiredArgsConstructor
@Component
@Slf4j
public class AuthenticationServiceImpl implements AuthenticationService {

  private final AuthenticationHandlerManager authenticationHandlerManager;
  private final MessageSourceAssertService assertService;
  private final IamApplicationManager iamApplicationManager;
  private final JsonService jsonService;

  @Override
  public <TParams extends AuthenticationParams> R<AuthenticationResult> authentication(
      String idOfClient,
      Long iamAppId,
      String authenticationGrantType,
      @Nullable String tag,
      TParams bizParams) {
    IamApplication iamApplication = iamApplicationManager.getAndCheckById(iamAppId);

    return this.authentication(idOfClient, iamApplication, authenticationGrantType, tag, bizParams);
  }

  @Override
  public R<AuthenticationResult> authentication(String input) {
    AuthenticationInputMap authenticationInputMap = jsonService.toObject(input, AuthenticationInputMap.class);

    AuthenticationHandler<AuthenticationParams> authenticationHandler = this.getAndCheckAuthenticationHandler(authenticationInputMap.getGrantType());

    AuthenticationParams bizParams = authenticationHandler.convert(input);

    IamApplication iamApplication = iamApplicationManager.getAndCheckById(authenticationInputMap.getIamAppId());

    return this.authentication(
        authenticationInputMap.getIdOfClient(),
        iamApplication,
        authenticationInputMap.getGrantType(),
        authenticationInputMap.getTag(),
        bizParams);
  }

  @Override
  public R<AuthenticationResult> authentication(AuthenticationInputMap input) {
    return null;
  }

  private <TParams extends AuthenticationParams> R<AuthenticationResult> authentication(
      String idOfClient,
      IamApplication iamApplication,
      String authenticationGrantType,
      @Nullable String tag,
      TParams bizParams) {
    AuthenticationHandler<TParams> authenticationHandler =
        this.getAndCheckAuthenticationHandler(authenticationGrantType);

    AuthenticationContextBo<TParams> authenticationContextBo =
        AuthenticationContextBo.<TParams>builder()
            .iamApplication(iamApplication)
            .authenticationGrantType(authenticationGrantType)
            .bizParams(bizParams)
            .idOfClient(idOfClient)
            .tag(tag)
            .build();

    AuthenticationHandlerContextInput<TParams> authenticationHandlerContextInput = AuthenticationHandlerContextInput
        .<TParams>builder()
        .authenticationContext(authenticationContextBo)
        .build();

    AuthenticationResult authenticationResultDto;
    try {
      authenticationResultDto = authenticationHandler.authentication(authenticationHandlerContextInput);
    } catch (BusinessException ex) {
      R<AuthenticationResult> resultDto = new R<>();
      resultDto.setCode(ex.getCode());
      resultDto.setData(null);
      resultDto.setExtras(ex.getExtras());
      resultDto.setMessage(ex.getMessage());
      return resultDto;
    }

    assertService.isTrue(
        authenticationResultDto == null || authenticationResultDto.getAccountId() == null,
        this.getI18nKey("authentication-fail"),
        "认证失败");

    Map<String, String> authenticationAttributes = new HashMap<>();
    if (MapUtils.isNotEmpty(authenticationResultDto.getAttributes())) {
      authenticationAttributes.putAll(authenticationResultDto.getAttributes());
    }

    Long accountId = authenticationResultDto.getAccountId();
    Map<String, String> attributes = new HashMap<>(authenticationAttributes);
    attributes.remove(SecurityConstant.TOKEN_ATTRIBUTES_ID_OF_CLIENT_KEY);
    attributes.put(SecurityConstant.TOKEN_ATTRIBUTES_ID_OF_CLIENT_KEY, idOfClient);
    attributes.put(
        SecurityConstant.TOKEN_ATTRIBUTES_IAM_APP_ID_KEY, iamApplication.getId().toString());

    AuthenticationResult authenticationResult =
        AuthenticationResult.builder().accountId(accountId).attributes(attributes).build();

    R<AuthenticationResult> resultDto = new R<>();
    resultDto.setCode(R.OK);
    resultDto.setData(authenticationResult);
    resultDto.setMessage("认证成功");
    resultDto.setExtras(new HashMap<>());
    return resultDto;
  }

  private <TParams extends AuthenticationParams>
  AuthenticationHandler<TParams> getAndCheckAuthenticationHandler(
      String authenticationGrantType) {
    AuthenticationHandler<TParams> authenticationHandler =
        authenticationHandlerManager.get(authenticationGrantType);
    assertService.isNull(
        authenticationHandler, this.getI18nKey("not-support-authentication-type"), "不支持认证流程");
    return authenticationHandler;
  }

  private String getI18nKey(String key) {
    return String.format("authentication-token-service.$s", key);
  }
}
